<!-- 可编辑表格 行disabled：false可编辑-->
<template>
  <div>
    <el-table
      :data="tableData"
      v-bind="tableProps"
      :expand-row-keys="expandKeys"
      default-expand-all
      ref="table"
      height="auto"
      :key="tableKey"
    >
      <template slot="empty">
        <slot name="empty"></slot>
      </template>
      <el-table-column
        v-for="(item, index) in columns.filter(column => !column.hidden)"
        v-bind="item"
        :key="item.prop"
      >
        <template v-slot:header>
          <slot :name="`${item.prop}-header`" :label="item.label">{{
            item.label
          }}</slot>
        </template>
        <template slot-scope="scope">
          <div v-if="scope.row.editing" :style="getEditBoxStyle(item.align)">
            <!-- input类型 -->
            <el-input
              v-if="formProps.props[item.prop].type === 'text'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="formProps.methods[item.prop]"
            ></el-input>
            <!-- number类型 -->
            <el-input
              v-if="formProps.props[item.prop].type === 'number'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="formProps.methods[item.prop]"
              @input="
                value =>
                  mergeInputNumber(
                    scope.row,
                    item.prop,
                    value,
                    formProps.props[item.prop].precision
                  )
              "
              @change="value => mergeChangeNumber(scope.row, item.prop, value)"
            ></el-input>

            <!-- date-picker -->
            <el-date-picker
              v-if="isDateType(formProps.props[item.prop].type)"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="formProps.methods[item.prop]"
              class="w-100"
            >
            </el-date-picker>
            <!-- time-picker -->
            <el-time-picker
              v-if="formProps.props[item.prop].type === 'time'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="formProps.methods[item.prop]"
              class="w-100"
            >
            </el-time-picker>
            <!-- select类型 -->
            <el-select
              v-if="formProps.props[item.prop].type === 'select'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="formProps.methods[item.prop]"
              class="w-100"
            >
              <el-option
                v-for="option in formProps.props[item.prop].options"
                :key="option.value"
                :label="option.label"
                :value="option.value"
              ></el-option>
            </el-select>
            <!-- switch -->
            <el-switch
              v-if="formProps.props[item.prop].type === 'switch'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="formProps.methods[item.prop]"
            ></el-switch>
            <!-- radio -->
            <el-radio-group
              v-if="formProps.props[item.prop].type === 'radio'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="formProps.methods[item.prop]"
            >
              <el-radio
                style="padding: 5px 0"
                v-for="option in formProps.props[item.prop].options"
                :key="option.value"
                :label="option.value"
                >{{ option.label }}</el-radio
              >
            </el-radio-group>
            <!-- checkbox -->
            <el-checkbox-group
              v-if="formProps.props[item.prop].type === 'checkbox'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="formProps.methods[item.prop]"
            >
              <el-checkbox
                v-for="option in formProps.props[item.prop].options"
                :key="option.value"
                :label="option.value"
                :disabled="option.disabled || false"
                >{{ option.label }}</el-checkbox
              >
            </el-checkbox-group>

            <!-- cascader -->
            <el-cascader
              v-if="formProps.props[item.prop].type === 'cascader'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="formProps.methods[item.prop]"
              class="w-100"
            ></el-cascader>
          </div>

          <span v-else>
            <slot
              :name="item.prop"
              :row="scope.row"
              :value="scope.row[item.prop]"
            >
              <span v-if="item.formatter">
                {{
                  item.formatter(
                    scope.row,
                    $refs.table.columns[index],
                    scope.row[item.prop],
                    index
                  )
                }}
              </span>
              <template v-else>
                <span
                  v-if="
                    formProps.props[item.prop].type === 'select' ||
                      formProps.props[item.prop].type === 'cascader' ||
                      formProps.props[item.prop].type === 'radio'
                  "
                >
                  {{
                    echoData(
                      scope.row[item.prop],
                      formProps.props[item.prop].options
                    ) || ""
                  }}</span
                >
                <el-switch
                  v-else-if="formProps.props[item.prop].type === 'switch'"
                  v-model="scope.row[item.prop]"
                  disabled
                  v-bind="getFormItemProp(item.prop)"
                  v-on="formProps.methods[item.prop]"
                ></el-switch>
                <template
                  v-else-if="formProps.props[item.prop].type === 'checkbox'"
                >
                  <template v-if="scope.row[item.prop].length">
                    <el-tag
                      class="m-x-2"
                      v-for="(item, index) in echoData(
                        scope.row[item.prop],
                        formProps.props[item.prop].options
                      ).split('/')"
                      :key="index + item"
                      >{{ item || "" }}</el-tag
                    >
                  </template>
                  <template v-else>
                    <span>{{ "" }}</span>
                  </template>
                </template>
                <span v-else>{{ scope.row[item.prop] }} </span>
              </template>
            </slot>
          </span>
        </template>
      </el-table-column>
      <el-table-column
        align="center"
        label="操作"
        width="130"
        v-if="!isActionHidden"
        :fixed="isActionFixed ? 'right' : false"
      >
        <template slot-scope="scope">
          <div v-if="scope.row.editing">
            <el-button
              type="primary"
              icon="el-icon-check"
              :size="tableProps.size"
              plain
              circle
              @click="saveEdit(scope.row, scope.$index)"
            ></el-button>
            <el-button
              type="info"
              icon="el-icon-close"
              :size="tableProps.size"
              plain
              circle
              @click="cancelEdit(scope.row, scope.$index)"
            ></el-button>
          </div>
          <div v-else>
            <el-button
              v-if="!isAddHidden"
              type="primary"
              icon="el-icon-plus"
              :size="tableProps.size"
              plain
              circle
              @click="addchildrenRow(scope.row, scope.$index)"
            ></el-button>
            <el-button
              type="primary"
              icon="el-icon-edit-outline"
              :size="tableProps.size"
              plain
              circle
              @click="editRow(scope.row, scope.$index)"
            ></el-button>
            <el-button
              type="danger"
              icon="el-icon-delete"
              :size="tableProps.size"
              plain
              circle
              @click="removeRow(scope.row, scope.$index)"
            ></el-button>
          </div>
        </template>
      </el-table-column>
    </el-table>
    <el-button
      type="success"
      plain
      icon="el-icon-plus"
      class="button-add"
      v-if="!isAddHidden"
      :size="tableProps.size"
      @click="addRow"
    ></el-button>
  </div>
</template>

<script>
export default {
  name: "editable-tree-grid",
  props: {
    columns: {
      type: Array,
      default: () => []
    },
    formProps: {
      type: Object,
      default: () => ({})
    },
    datas: {
      type: Array,
      default: () => []
    },
    onChange: {
      type: Function
    },
    isActionHidden: {
      type: Boolean,
      default: false
    },
    isActionFixed: {
      type: Boolean,
      default: false
    },
    isAddHidden: {
      type: Boolean,
      default: false
    },
    value: {}
  },
  watch: {
    formProps: {
      handler(val) {
        this.rowKey = val.rowKey || "id";
        if (val.treeProps && val.treeProps.children) {
          this.childKey = val.treeProps.children;
        } else {
          this.childKey = "children";
        }
        if (val.methods) {
          for (const key in val.props) {
            if (val.methods[key]) {
              if (val.props[key].type === "number" && val.methods[key].change) {
                this.mergeChangeFun = val.methods[key].change;
                this.$delete(this.formProps.methods[key], "change");
              }
              if (val.props[key].type === "number" && val.methods[key].input) {
                this.mergeInputFun = val.methods[key].input;
                this.$delete(this.formProps.methods[key], "input");
              }
            }
          }
        }

        const newProp = Object.assign({}, { ...val });
        Reflect.deleteProperty(newProp, "props");
        Reflect.deleteProperty(newProp, "methods");
        this.tableProps = Object.assign(
          {
            border: true,
            size: "mini",
            rowKey: "id",
            treeProps: { hasChildren: "hasChildren", children: "children" }
          },
          newProp
        );
      },
      deep: true,
      immediate: true
    },
    value: {
      handler(val) {
        if (this.isOpen) {
          if (!val) {
            return;
          }
          const newData = val.map((item, index) => {
            const { rowKey } = this;
            return Object.assign({}, item, {
              editing: false,
              [rowKey]: item[rowKey] || "" + index,
              parentId: "",
              [this.childKey]: item[this.childKey] || []
            });
          });
          this.tableData = JSON.parse(JSON.stringify(newData));
          this.isOpen = false;
        }
      },
      deep: true,
      immediate: true
    },
    tableData: {
      handler(val) {
        this.$emit("input", val);
      },
      deep: true
    }
  },

  mounted() {
    for (const key in this.formProps.props) {
      if (
        this.formProps.props[key].type === "checkbox" ||
        this.formProps.props[key].type === "cascader"
      ) {
        this.templeteData[key] = [];
      } else {
        this.templeteData[key] = "";
      }
    }
    this.tableKey = !this.tableKey;
  },
  data() {
    return {
      isOpen: true,
      childKey: null,
      rowKey: null,
      tableData: [],
      tableKey: false,
      tableProps: null,
      templeteData: {},
      expandKeys: [],
      copyRowData: null,
      dateMap: [
        "year",
        "month",
        "date",
        "dates",
        "week",
        "datetime",
        "datetimerange",
        "daterange",
        "monthrange"
      ],
      mergeInputFun: undefined,
      mergeChangeFun: undefined
    };
  },

  methods: {
    //合并事件input
    mergeInputNumber(row, name, value, precision) {
      this.formatNumber(row, name, value, precision);
      if (typeof this.mergeInputFun === "function") {
        this.mergeInputFun(value);
      }
    },
    //合并事件change
    mergeChangeNumber(row, name, value) {
      this.handleNumberChange(row, name, value);
      if (typeof this.mergeChangeFun === "function") {
        this.mergeChangeFun(value);
      }
    },
    isDateType(type) {
      return this.dateMap.indexOf(type) >= 0;
    },

    echoData(ids, data, lab, val, list) {
      if (typeof ids === "string") {
        return this.$utils.echoCascaderData(ids, data, lab, val, list);
      } else {
        const finish = [];
        ids.forEach(id => {
          finish.push(this.$utils.echoCascaderData(id, data, lab, val, list));
        });
        return finish.join("/");
      }
    },

    getFormItemProp(name) {
      if (this.formProps.props[name].type === "number") {
        return Object.assign(
          {},
          this.formProps.props[name],
          { type: "text" },
          { size: this.tableProps.size }
        );
      } else {
        return Object.assign({}, this.formProps.props[name], {
          size: this.tableProps.size
        });
      }
    },
    formatNumber(row, name, value, precision = 0) {
      const isMinus = value.charAt(0) === "-";
      const numberValue = value
        .replace(/[^\d.]/g, "")
        .replace(new RegExp(`\\.{${precision},}`, "g"), ".")
        .replace(".", "$#$")
        .replace(/\./g, "")
        .replace("$#$", ".")
        .replace(new RegExp(`^(-)*(\\d+)\\.(\\d{${precision},}).*$`), "$1$2.$3")
        .replace(/^\./g, "");
      const minusMap = { true: "-", false: "" };
      const floatValue = parseFloat(minusMap[isMinus] + numberValue);
      if (isNaN(floatValue)) {
        row[name] = "";
        return;
      }
      const splitArray = numberValue.split(".");
      let strNumber = minusMap[isMinus] + numberValue;
      if (splitArray.length > 1) {
        const fixedStr = precision
          ? splitArray[1].substring(0, precision)
          : splitArray[1];
        if (splitArray.length > 2) {
          strNumber = minusMap[isMinus] + splitArray[0] + "." + fixedStr;
        } else {
          fixedStr &&
            (strNumber = minusMap[isMinus] + splitArray[0] + "." + fixedStr);
        }
      } else {
        strNumber = minusMap[isMinus] + numberValue;
      }
      row[name] = strNumber;
    },
    handleNumberChange(row, name, value) {
      row[name] = isNaN(parseFloat(value)) ? 0 : parseFloat(value);
    },
    editRow(row, index) {
      if (this.copyRowData && this.copyRowData.editing === true) {
        return this.$message({
          type: "error",
          message: "请先保存当前编辑行的数据"
        });
      }
      const { rowKey } = this;
      this.getRowData(this.tableData, rowKey, row[rowKey], data => {
        this.$set(data, "editing", true);
      });
      this.tableKey = !this.tableKey;
      this.copyRowData = JSON.parse(JSON.stringify(row));
    },
    removeRow(row, index) {
      const { rowKey, childKey } = this;

      this.$confirm("确定删除本行内容?", "提示", {
        type: "warning"
      })
        .then(() => {
          this.$message({
            type: "success",
            message: "删除成功!"
          });
          if (row.parentId) {
            this.getRowData(
              this.tableData,
              rowKey,
              row.parentId,
              parentData => {
                const index = parentData[childKey].findIndex(
                  item => item[rowKey] === row[rowKey]
                );

                parentData[childKey].splice(index, 1);
              }
            );
            this.getRowData(
              [this.copyRowData],
              rowKey,
              row.parentId,
              childData => {
                const index = childData[childKey].findIndex(
                  item => item[rowKey] === row[rowKey]
                );
                childData[childKey].splice(index, 1);
              }
            );
          } else {
            this.getRowData(this.tableData, rowKey, row[rowKey], parentData => {
              const index = this.tableData.findIndex(
                item => item[rowKey] === row[rowKey]
              );
              this.tableData.splice(index, 1);
            });
          }
          this.onChange({
            row,
            datas: this.tableData,
            type: "delete",
            index
          });
        })
        .catch(() => {
          this.$message({
            type: "info",
            message: "已取消删除"
          });
        });
      this.tableKey = !this.tableKey;
    },
    saveEdit(row, index) {
      this.tableKey = !this.tableKey;
      this.onChange({ row, datas: this.tableData, type: "save", index })
        .then(() => {
          this.$message({
            type: "success",
            message: "保存成功!"
          });
          row.editing = false;
          this.copyRowData = null;
        })
        .catch(error => {
          this.$message({
            type: "error",
            message: error.message
          });
        });
    },
    cancelEdit(row) {
      const { rowKey, childKey } = this;
      this.getRowData(this.tableData, rowKey, row[rowKey], data => {
        Object.assign(data, this.copyRowData);
        this.$set(data, "editing", false);
        let isColumn = true;
        this.columns
          .filter(column => !column.hidden)
          .forEach(item => {
            if (
              this.copyRowData[item.prop] != "" &&
              this.copyRowData[item.prop].length
            ) {
              isColumn = false;
            }
          });
        if (row.parentId && isColumn) {
          this.getRowData(this.tableData, rowKey, row.parentId, parentData => {
            const index = parentData[childKey].findIndex(
              item => item[rowKey] === row[rowKey]
            );
            parentData[childKey].splice(index, 1);
          });
        } else if (isColumn) {
          this.getRowData(this.tableData, rowKey, row[rowKey], parentData => {
            const index = this.tableData.findIndex(
              item => item[rowKey] === row[rowKey]
            );
            this.tableData.splice(index, 1);
          });
        }
        this.tableKey = !this.tableKey;
        this.copyRowData = null;
      });
    },
    addRow() {
      if (this.copyRowData && this.copyRowData.editing === true) {
        return this.$message({
          type: "error",
          message: "请先保存当前编辑行的数据"
        });
      }
      const templete = Object.assign({}, this.templeteData, {
        editing: true,
        [this.rowKey]: new Date().getTime() + "",
        parentId: "",
        [this.childKey]: []
      });
      this.copyRowData = JSON.parse(JSON.stringify(templete));
      this.tableData.push(templete);
      this.tableKey = !this.tableKey;
    },
    addchildrenRow(row, index) {
      if (this.copyRowData && this.copyRowData.editing === true) {
        return this.$message({
          type: "error",
          message: "请先保存当前编辑行的数据"
        });
      }
      const { rowKey, childKey } = this;
      const newRow = Object.assign({}, row, {
        [childKey]: row[childKey] || []
      });

      const childTempleteData = Object.assign({}, this.templeteData, {
        [rowKey]: new Date().getTime() + "",
        parentId: row[rowKey],
        editing: true,
        [childKey]: []
      });
      this.copyRowData = JSON.parse(JSON.stringify(childTempleteData));
      newRow[childKey].push(childTempleteData);
      this.getRowData(this.tableData, rowKey, row[rowKey], data => {
        this.$set(data, childKey, newRow[childKey]);
      });
      this.expandKeys.push(row[rowKey]);
      this.tableKey = !this.tableKey;
    },
    getEditBoxStyle(align) {
      const alignMap = {
        left: "flex-start",
        center: "center",
        right: "flex-end"
      };
      return {
        display: "flex",
        width: "100%",
        justifyContent: alignMap[align]
      };
    },
    getRowData(datas, key, value, callback) {
      const { childKey } = this;
      datas.forEach(item => {
        if (item[key] === value) {
          callback(item);
        }
        if (item[childKey] && item[childKey].length) {
          this.getRowData(item[childKey], key, value, callback);
        }
      });
    }
  }
};
</script>

<style lang="scss" scoped>
.el-icon-delete {
  color: #f56c6c;
  cursor: pointer;
  &:hover {
    color: red;
  }
}
.button-add {
  width: 100%;
  border-style: dashed;
  margin-top: 10px;
  &:hover {
    background: #e7f9dd;
    color: #67c23a;
    font-weight: bold;
  }
}
::v-deep .readonlycascader input {
  border: 0;
  background-color: inherit !important;
  color: inherit !important;
  cursor: auto !important;
}
::v-deep .readonlycascader .el-input__suffix {
  display: none;
}
::v-deep.el-input__inner {
  padding: 0px 2px !important;
}
::v-deep .el-table .cell {
  display: flex;
}
.el-tooltip span {
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
</style>
